package com.tool.rabbitmq.config;

import com.tool.rabbitmq.RabbitAutoService;
import com.tool.rabbitmq.constants.RabbitConstant;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
import org.springframework.boot.autoconfigure.amqp.RabbitTemplateConfigurer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Scope;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/**
 * @author : zhaoxuan
 * @date : 2021/9/10 13:27
 * @description : RabbitMQToolAutoConfiguration
 * @version: 1.0
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@AutoConfigureAfter(RabbitAutoConfiguration.class)
public class RabbitToolAutoConfiguration {
    private static final Logger log = LoggerFactory.getLogger(RabbitToolAutoConfiguration.class);

    private static final String DELAY_MESSAGE_ARGS_KEY = "x-delayed-type";
    private static final String DELAY_MESSAGE_ARGS_VALUE = "direct";
    private static final String MESSAGE_ID_KEY = "spring_returned_message_correlation";

    @Resource
    private ApplicationContext applicationContext;

    @Bean(RabbitConstant.CALLBACK_RABBIT_TEMPLATE)
    @ConditionalOnBean(ConnectionFactory.class)
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public RabbitTemplate callBackRabbitTemplate(RabbitTemplateConfigurer configurer, ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate();
        configurer.configure(template, connectionFactory);
        return template;
    }

    @Bean("autoRabbitAdmin")
    @ConditionalOnBean(ConnectionFactory.class)
    public RabbitAdmin autoRabbitAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    /**
     * 延时exchange
     * @return
     */
    @Bean("autoDelayExchange")
    public CustomExchange delayExchange() {
        Map<String, Object> args = new HashMap<>(2);
        args.put(DELAY_MESSAGE_ARGS_KEY, DELAY_MESSAGE_ARGS_VALUE);
        return new CustomExchange(RabbitConstant.DELAY_EXCHANGE, RabbitConstant.DELAY_MESSAGE_TYPE, true, false, args);
    }

    @Bean
    @ConditionalOnBean(RabbitTemplate.class)
    @DependsOn({RabbitConstant.CALLBACK_RABBIT_TEMPLATE, "autoRabbitAdmin", "autoDelayExchange"})
    public RabbitAutoService rabbitAutoService(RabbitTemplate rabbitTemplate,
                                               @Qualifier("autoRabbitAdmin") RabbitAdmin rabbitAdmin,
                                               @Qualifier("autoDelayExchange") CustomExchange delayExchange) {
        try {
            rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
                if (ack) {
                    log.info("ConfirmCallback#confirm#success#{}", correlationData == null ? "" : correlationData.getId());
                } else {
                    log.error("ConfirmReturnCallback#confirm#failure#{}\r\n" +
                                    "cause: {}",
                            correlationData == null ? "" : correlationData.getId(),
                            cause);
                }
            });
        } catch (IllegalStateException e) {
            log.warn("The client has been configured ConfirmCallback");
        }
        try {
            rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
                Map<String, Object> headers = message.getMessageProperties().getHeaders();
                Object springReturnedMessageCorrelation = headers.get(MESSAGE_ID_KEY);
                log.error("ReturnCallback#returnedMessage#{}\r\n" +
                                "message: {}\r\n" +
                                "replyText: {}\r\n" +
                                "exchange: {}\r\n" +
                                "routingKey: {}",
                        springReturnedMessageCorrelation == null ? "" : springReturnedMessageCorrelation.toString(),
                        new String(message.getBody()),
                        replyText,
                        exchange,
                        routingKey);
            });
        } catch (IllegalStateException e) {
            log.warn("The client has been configured ReturnCallback");
        }
        return new RabbitAutoService(rabbitTemplate, rabbitAdmin, delayExchange, applicationContext);
    }
}
